package hh.util.uuid;

import hh.util.Asserts;
import hh.util.NumberUtil;
import hh.util.time.TimeUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.Date;
import java.util.Random;

@Slf4j
public class DistributeId64Bit extends DistributeIdGenerator<Long> {
    // 从过去多少ms开始，因为41位ms最多表示69年，如果现在已经是68年了，很快就溢出了可以从十年，三十年之前开始计算毫秒值
    protected long startTimestampLast = 9527 * 9528;
    protected final int maxSequenceId;
    protected final int sequenceIdMask;
    protected final int maxWorkId;
    protected int timestampBit;
    protected int workIdBit;
    protected int sequenceBit;

    protected int timestampShift;
    protected int workIdShift;

    protected long workId;


    public DistributeId64Bit() {
        this(41, 8, 14);
    }

    /**
     * 41bit时间戳可以表示     69年266天15小时47分钟35秒551毫秒
     * 35bit可以表示    1年32天16小时22分钟18秒367毫秒
     * @param timestampBit
     * @param workIdBit
     * @param sequenceBit
     */
    public DistributeId64Bit(int timestampBit, int workIdBit, int sequenceBit) {
        Asserts.True(63 != timestampBit + workIdBit + sequenceBit, "总长度必须==63bit");
        this.timestampBit = timestampBit;
        this.workIdBit = workIdBit;
        this.sequenceBit = sequenceBit;

        this.maxSequenceId = 1 << sequenceBit;
        this.maxWorkId = 1 << workIdBit;
        this.workId = new Random().nextInt(maxWorkId);

        this.timestampShift = this.sequenceBit + this.workIdBit;
        this.workIdShift = this.sequenceBit;
        this.sequenceIdMask = maxSequenceId - 1;

        log.info("--------------------------DistributeId64Bit初始化各bit占用情况--------------------------");
        log.info("maxSequence={} bit:{}", maxSequenceId, sequenceBit);
        log.info(NumberUtil.toBinaryPretty64(maxSequenceId - 1));

        log.info("maxWorkId={} bit:{}", maxWorkId, workIdBit);
        log.info(NumberUtil.toBinaryPretty64((maxWorkId - 1) << workIdShift));

        long t = 1631779212917L;
        log.info("timestamp={} bit:{}", t, timestampBit);
        log.info(NumberUtil.toBinaryPretty64(t << timestampShift));

        log.info("其中workId默认随机生成，可以手动设置，数据来源可以是redis incr，zk顺序节点，数据库主键id等等");
    }

    public static void main(String[] args) {
        System.out.println(NumberUtil.toBinaryPretty64(Long.MAX_VALUE));
        System.out.println(Integer.toBinaryString(170000));
        DistributeId64Bit id =
//                new DistributeId64Bit();
                new DistributeId64Bit(33, 8, 22);
        id.setWorkId(222);
        Long serializable = id.getId();
        serializable = id.getId();
        long[] resolve = id.resolve(serializable);
        System.out.println(Arrays.toString(resolve));
        System.out.println(TimeUtil.formatLong(resolve[0]));
        System.out.println(serializable);

        System.out.println(new Date());
    }


    public void setWorkId(int workId) {
        Asserts.False(workId < maxWorkId, "workId必须小于{}", maxWorkId);
        this.workId = workId;
    }


    public void setStartTimestampLast(long startTimestampLast) {
        Asserts.False(startTimestampLast < System.currentTimeMillis(), "必须小于当前时间戳");
        this.startTimestampLast = startTimestampLast;
    }

    protected long currentTime() {
        return System.currentTimeMillis() - startTimestampLast;
    }

    /**
     * <blockquote><pre>
     *  64位,long类型，第一位0表示正数，41位时间可以表示69，一般减去一个值，可以表示更多，比如减去40年，机器id可以256个，自增序列可以4096个
     *  |--------------- 时间戳41bit-----------------|机器id 10b| 自增序列12 bit |
     * 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 64位
     * 时间戳
     * 00000001 01111011 11100011 10101010 11000010 11100110 有效长度41位
     * 一年的毫秒值
     *                        101 00100110 01011100 00000000
     * 十年的毫秒值
     *                     110011 01111111 10011000 00000000
     * </pre></blockquote>
     * 如果bit不够，可以将时间戳变小几位
     * <p>
     * 这里设计  64bit=时间戳+机器id+自增id
     * <p>
     */
    @Override
    protected Long distributeId() {
        long id = currentTime() << timestampShift
                | workId << workIdShift
                | (incr.getAndIncrement() & sequenceIdMask);
        return id > 0 ? id : -id; // timestamp左移可能会溢出
    }


    public long[] resolve(long id) {
        long[] arr = new long[3];
        if(timestampBit<35){
            log.warn("timestampBit小于1年(35bit), 时间戳反解析只能解析一年内的时间，否则可能不准确");
        }
        if (timestampBit < 41) {
            if ((currentTime() << timestampShift) < 0) id = -id;
            id = id & Long.MAX_VALUE; // 保持正数
            long mask = (System.currentTimeMillis() >> timestampBit) << timestampBit; // 补回 << 缺失的部分，timestampBit很小的话有可能失真
            arr[0] = ((id >> timestampShift) | mask) + startTimestampLast;
        } else
            arr[0] = (id >> timestampShift) + startTimestampLast;
        arr[1] = (id >> workIdShift) & (maxWorkId - 1);
        arr[2] = id & sequenceIdMask;
        return arr;
    }


    protected long timestampBinary() {
        return currentTime() << timestampShift;
    }

    protected long workIdBinary() {
        return workId << workIdShift;
    }

    protected long incrementBinary() {
        return incr.getAndIncrement() & sequenceIdMask;
    }
}
